package com.eben.model;

import java.util.ArrayList;

import android.graphics.Color;
import android.graphics.RectF;
import android.util.Log;


public class Board {

	private Position grid;
	private Pointer pointer;
	private Life life;
	private Score score;
	private Time badObjectSpawnTimer;
	private Time invincibleActiveTimer;
	private ArrayList<GameObject> gameObjects;
	
	//These variables will only be used in spawnRandomObject() 
	//with the purpose not to create so many new object{
	private Position randomInRect = new Position();
	private Position randomOutsideGrid = new Position();
	private Position dirUnitVector = new Position();
	private RectF rect = new RectF();
	//} 
	
	/**
	 * Creates a board with the dimensions given by a position.
	 * @param position the position to describe the dimensions with.
	 */
	public Board(Position position){
		grid = new Position(position);
		life = new Life(grid);
		score = new Score();
		badObjectSpawnTimer = new Time();
		invincibleActiveTimer = new Time();
		//Just testing code to see if something appears on the screen.
		pointer = new Pointer(new Position(grid.getX()/2,grid.getY()/2), 15, Color.RED);
		gameObjects = new ArrayList<GameObject>();
		gameObjects.add(pointer);
	}
	
	/**
	 * This constructor is only to be used for JUnitTest. Do NOT use this constructor otherwise.
	 * @param go The GameObject that should be tested if the pointer has hit.
	 */
	public Board(Pointer pointer, Point point, BadObject bo, HealthPoint hp, InvinciblePoint ip){
		grid = new Position(240,320);
		life = new Life(grid);
		score = new Score();
		this.pointer = pointer;
		gameObjects = new ArrayList<GameObject>();
		gameObjects.add(pointer);
		gameObjects.add(point);
		gameObjects.add(bo);
		gameObjects.add(hp);
		gameObjects.add(ip);
		badObjectSpawnTimer = new Time();
		invincibleActiveTimer = new Time();
	}
	
	/**
	 * Updates the position of all the objects on the board and calculates if two objects hit.
	 * @param position the position of the cursor.
	 * @throws GameOverException 
	 * @throws VibrationException 
	 */
	public void updatePositions() throws GameOverException, VibrationException{
		for(int i=0;i<gameObjects.size();i++){
    		gameObjects.get(i).updatePosition();
			//{Checks if the badObject needs to be respawn on a new position.
			if(gameObjects.get(i) instanceof BadObject){
				if(!gameObjects.get(i).isOnGrid() && isOnGrid(gameObjects.get(i).getPosition())){//The object is on board but has recently been spawned.
					gameObjects.get(i).setOnGrid(true);
				}
				if(gameObjects.get(i).isOnGrid() && !isOnGrid(gameObjects.get(i).getPosition())){//The object has been on board but is not now.
					spawnRandomBadObject((BadObject)randomGameObjectAttributes((BadObject)gameObjects.get(i))); //Respawns the object.
				}
			}//}
    	}
		hasHit();
		
		if(life.isDead()){
			throw new GameOverException(score);
		}
		
		//Spawns a new BadObject every 8 second.
		if(badObjectSpawnTimer.getTime()>=8){
			gameObjects.add(spawnRandomBadObject((BadObject)randomGameObjectAttributes(new BadObject())));
			badObjectSpawnTimer.resetTime();
		}
		if(invincibleActiveTimer.getTime() > InvinciblePoint.getActiveTime()){
			life.setInvincible(false);
		}
	}
	
	//Checks if the pointer has hit another object.
	private void hasHit() throws VibrationException{
		for(int i=0;i<gameObjects.size();i++){
			if(!(gameObjects.get(i) instanceof Pointer)){ //We are not interested in the comparing the pointer with itself.
				if(isInRangeOfPointer(gameObjects.get(i))){
					if(gameObjects.get(i) instanceof Point){ //if the pointer hits a Point
						score.addPoint();
						Log.d("Add score", score.getScoreInt() + "");
						spawnRandomObject((Point)gameObjects.get(i));
						double random = (Math.random()*100);//Simulates a 100 percent chance.
						if(random > 99){//1 % chance to happen
							gameObjects.add(spawnRandomObject(new InvinciblePoint()));
							Log.d("invinciblePoint", "invinciblePoint");
						} else if(random > 97){ //2 % chance to happen.
							gameObjects.add(spawnRandomObject(randomGameObjectAttributes(new HealthPoint())));
							Log.d("healthPoint", "healthPoint");
						}
						break; //The pointer will never hit more than 1 object each update.
					} else if(gameObjects.get(i) instanceof BadObject && !life.isInvincible()){ //if the pointer hits a badObject
						life.subtractLife(((BadObject)gameObjects.get(i)).getDamage());
						gameObjects.remove(gameObjects.get(i));
						Log.d("Substract Life", life.getLife() + "");
						throw new VibrationException();
					//The pointer will never hit more than 1 object each update.
					} else if(gameObjects.get(i) instanceof HealthPoint){ //if the pointer hits a HealthPoint
						life.addLife(((HealthPoint)gameObjects.get(i)).getHealth());
						gameObjects.remove(gameObjects.get(i));
						break; //The pointer will never hit more than 1 object each update.
					} else if(gameObjects.get(i) instanceof InvinciblePoint){ //if the pointer hits a InvinciblePoint
						invincibleActiveTimer.startTime();
						life.setInvincible(true);
						gameObjects.remove(gameObjects.get(i));
						break; //The pointer will never hit more than 1 object each update.
					}
				}
			}
		}
	}
	
	
	/**
	 * Returns the dimensions of the grid.
	 * @return a copy of variable grid.
	 */


	public Life getLife(){
		return life;
	}
	
	public Score getScore(){
		return score;
	}
	
	public ArrayList<GameObject> getBoardObjects(){
		return gameObjects;
	}

	public void updateCursor(Position position) {
		pointer.updatePosition(position);
	}
	
	public GameObject spawnRandomObject(GameObject go){
		go.setPosition(Math.random()*(grid.getX()-10)+5, Math.random()*(grid.getY()-10)+5);
		while(isInRangeOfPointer(go)){
			go.setPosition(Math.random()*(grid.getX()-10)+5, Math.random()*(grid.getY()-10)+5);
			Log.d("isInRangeOfPointer", "Was spawned on the pointer");
		}
		return go;
	}
	
	/**
	 * Returns a BadObject at a random position outside the grid with a direction towards a 
	 * random point in the grid.
	 * @param bo the BadObject that the algameObjects.get(i)ritm should be performed on.
	 * @return the BadObject with new randomed position and direction.
	 */
	private BadObject spawnRandomBadObject(BadObject bo){
		//Creates a rectangle inside the grid with an area that is 1/4 of the area of the grid. 
		//Though the rect is centered.
		rect.set((float)(grid.getX()/4), (float)(grid.getY()/4), 
				(float)(3*grid.getX()/4), (float)(3*grid.getY()/4));
		//Creates a random position in the rectangle above.
		randomInRect.setX(rect.left+(rect.right-rect.left)*Math.random());
		randomInRect.setY(rect.top+(rect.bottom-rect.top)*Math.random());
		//Creates a random position outside the grid.{
		int side = (int)(4*Math.random()); //Randoms one side of the grid.
		if(side == 0){//North side
			randomOutsideGrid.setX(Math.random()*grid.getX());
			randomOutsideGrid.setY(-5);
		} else if(side == 1){//East side
			randomOutsideGrid.setX(grid.getX()+5);
			randomOutsideGrid.setY(Math.random()*grid.getY());
		} else if(side == 2){//South side
			randomOutsideGrid.setX(Math.random()*grid.getX());
			randomOutsideGrid.setY(grid.getY()+5);
		} else if(side == 3){//West side
			randomOutsideGrid.setX(-5);
			randomOutsideGrid.setY(Math.random()*grid.getY());
		}//}
		//Sets the normalized vector randomOutSideGrid --> randomInRect
		dirUnitVector.setX((randomInRect.getX()-randomOutsideGrid.getX())/
				Math.sqrt(Math.pow((randomInRect.getX()-randomOutsideGrid.getX()), 2) + 
				Math.pow((randomInRect.getY()-randomOutsideGrid.getY()), 2)));
		dirUnitVector.setY((randomInRect.getY()-randomOutsideGrid.getY())/
				Math.sqrt(Math.pow((randomInRect.getX()-randomOutsideGrid.getX()), 2) + 
				Math.pow((randomInRect.getY()-randomOutsideGrid.getY()), 2)));
		//Calculating the angle of the vector according to the unit circle.
		//NOTE: cos(angle) = dirUnitVector.getX()
		//NOTE: sin(angle) = -dirUnitVector.getY() Inverted since y-axel is inverted in GUI.
		double angle = Math.acos(dirUnitVector.getX());
		if(-dirUnitVector.getY()<0){ //equal to sin(angle)<0. Then PI < angle < 2*PI.
			angle = -angle; //Check unit circle to understand why "angle = -angle".
		}
		bo.setDirection(angle);
		bo.setPosition(randomOutsideGrid.getX(), randomOutsideGrid.getY());
		bo.setOnGrid(false);
		return bo;
	}
	//Randoms the health and damage (size and color thereafter) of HealthPoint and BadObject.
	private GameObject randomGameObjectAttributes(GameObject go){
		int diff  = 30;
		int min = 20;
		int random = min+(int)(Math.random()*(diff+1));// min <= damage <= min+diff
		go.radius = (int)(((double)(random-min)/diff)*8 + 7);// 7 <= radius <= 15
		if(go instanceof HealthPoint){
			((HealthPoint)go).setHealth(random);
			((HealthPoint)go).setColor(Color.argb(255, // Alpha 255
					2*(diff+min-random),// 255-3*(diff) <= red <= 255
					240,// 255-3*(diff) <= green <= 255
					0));// 255-3*(diff) <= blue <= 255
		} else if (go instanceof BadObject){
			((BadObject)go).setDamage(random);
			((BadObject)go).setColor(Color.argb(255, // Alpha 255
								255-(random-min)*3,// 255-3*(diff) <= red <= 255
								255-(random-min)*3,// 255-3*(diff) <= green <= 255
								255-(random-min)*3));// 255-3*(diff) <= blue <= 255
		}
		return go;
	}
	
	public Pointer getPointer(){
		return pointer;
	}
	
	private boolean isInRangeOfPointer(GameObject go){
		if(Math.sqrt(Math.pow((pointer.getPosition().getX()-go.getPosition().getX()),2)+
			Math.pow((pointer.getPosition().getY()-go.getPosition().getY()),2)) 
			<= (pointer.getRadius()+go.getRadius())) {
			return true;
		}
		return false;
	}
	
	private boolean isOnGrid(Position p){
		if(p.getX()>=0 && p.getX() <= grid.getX() && p.getY() >= 0 && p.getY() <= grid.getY()){ //p is on grid.
			return true;
		}
		return false;
	}
	
	public Time getInvincibleActiveTimer(){
		return invincibleActiveTimer;
	}

	/**
	 * Starts the timers and spawns the first objects on the board.
	 */
	public void startGame() {
		gameObjects.add(new Point(new Position(50,50)));
    	gameObjects.add(spawnRandomBadObject((BadObject)randomGameObjectAttributes(new BadObject())));
    	
    	//Starts the time
    	badObjectSpawnTimer.startTime();
    	score.startScoreTimer();
	}
	
	public class VibrationException extends Exception{

		/**
		 * 
		 */
		private static final long serialVersionUID = 1160258950418913104L;};

}
